/******************************************************************************* * Copyright (c) 2006, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Patrik Suzzi <psuzzi@gmail.com> - Bug 500661, 492180 *******************************************************************************/ package org.eclipse.ui.internal.quickaccess; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.eclipse.jface.resource.ImageDescriptor; /** * @since 3.3 * */ public abstract class QuickAccessElement { static final String separator = " - "; //$NON-NLS-1$ private static final int[][] EMPTY_INDICES = new int[0][0]; private QuickAccessProvider provider; /** * @param provider */ public QuickAccessElement(QuickAccessProvider provider) { super(); this.provider = provider; } /** * Returns the label to be displayed to the user. * * @return the label */ public abstract String getLabel(); /** * Returns the image descriptor for this element. * * @return an image descriptor, or null if no image is available */ public abstract ImageDescriptor getImageDescriptor(); /** * Returns the id for this element. The id has to be unique within the * QuickAccessProvider that provided this element. * * @return the id */ public abstract String getId(); /** * Executes the associated action for this element. */ public abstract void execute(); /** * Return the label to be used for sorting elements. * * @return the sort label */ public String getSortLabel() { return getLabel(); } /** * @return Returns the provider. */ public QuickAccessProvider getProvider() { return provider; } private static final String WS_START = "^\\s+"; //$NON-NLS-1$ private static final String WS_END = "\\s+$"; //$NON-NLS-1$ private static final String ANY_WS = "\\s+"; //$NON-NLS-1$ private static final String EMPTY_STR = ""; //$NON-NLS-1$ private static final String PAR_START = "\\("; //$NON-NLS-1$ private static final String PAR_END = "\\)"; //$NON-NLS-1$ private static final String ONE_CHAR = ".?"; //$NON-NLS-1$ // whitespaces filter and patterns private String wsFilter; private Pattern wsPattern; /** * Get the existing {@link Pattern} for the given filter, or create a new * one. The generated pattern will replace whitespaces with * to match all. * * @param filter * @return */ private Pattern getWhitespacesPattern(String filter) { if (wsPattern == null || !filter.equals(wsFilter)) { wsFilter = filter; String sFilter = filter.replaceFirst(WS_START, EMPTY_STR).replaceFirst(WS_END, EMPTY_STR) .replaceAll(PAR_START, ONE_CHAR).replaceAll(PAR_END, ONE_CHAR); sFilter = String.format(".*(%s).*", sFilter.replaceAll(ANY_WS, ").*(")); //$NON-NLS-1$//$NON-NLS-2$ wsPattern = safeCompile(sFilter); } return wsPattern; } // wildcard filter and patterns private String wcFilter; private Pattern wcPattern; /** * Get the existing {@link Pattern} for the given filter, or create a new * one. The generated pattern will handle '*' and '?' wildcards. * * @param filter * @return */ private Pattern getWildcardsPattern(String filter) { if (wcPattern == null || !filter.equals(wcFilter)) { wcFilter = filter; String sFilter = filter.replaceFirst(WS_START, EMPTY_STR).replaceFirst(WS_END, EMPTY_STR) .replaceAll(PAR_START, ONE_CHAR).replaceAll(PAR_END, ONE_CHAR); // replace '*' and '?' with their matchers ").*(" and ").?(" StringBuilder sb = new StringBuilder(); for(int i=0; i<sFilter.length(); i++) { char c = sFilter.charAt(i); if(c=='*'||c=='?') { sb.append(").").append(c).append("("); //$NON-NLS-1$ //$NON-NLS-2$ } else { sb.append(c); } } sFilter = String.format(".*(%s).*", sb.toString()); //$NON-NLS-1$ // wcPattern = safeCompile(sFilter); } return wcPattern; } /** * A safe way to compile some unknown pattern, avoids possible * {@link PatternSyntaxException}. If the pattern can't be compiled, some * not matching pattern will be returned. * * @param pattern * some pattern to compile, not null * @return a {@link Pattern} object compiled from given input or a dummy * pattern which do not match anything */ private Pattern safeCompile(String pattern) { try { return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); } catch (Exception e) { // A "bell" special character: should not match anything we can get return Pattern.compile("\\a"); //$NON-NLS-1$ } } int i = 0; /** * If this element is a match (partial, complete, camel case, etc) to the * given filter, returns a {@link QuickAccessEntry}. Otherwise returns * <code>null</code>; * * @param filter * filter for matching * @param providerForMatching * the provider that will own the entry * @return a quick access entry or <code>null</code> */ public QuickAccessEntry match(String filter, QuickAccessProvider providerForMatching) { String sortLabel = getLabel(); // first occurrence of filter int index = sortLabel.toLowerCase().indexOf(filter); if (index != -1) { int quality = sortLabel.toLowerCase().equals(filter) ? QuickAccessEntry.MATCH_PERFECT : (sortLabel.toLowerCase().startsWith(filter) ? QuickAccessEntry.MATCH_EXCELLENT : QuickAccessEntry.MATCH_GOOD); return new QuickAccessEntry(this, providerForMatching, new int[][] { { index, index + filter.length() - 1 } }, EMPTY_INDICES, quality); } Pattern p; if (filter.contains("*") || filter.contains("?")) { //$NON-NLS-1$ //$NON-NLS-2$ // check for wildcards p = getWildcardsPattern(filter); } else { // check for whitespaces p = getWhitespacesPattern(filter); } Matcher m = p.matcher(sortLabel); // if matches, return an entry and highlight the match if (m.matches()) { int groupCount = m.groupCount(); int[][] indices = new int[groupCount][]; for (int i = 0; i < groupCount; i++) { int nGrp = i + 1; // capturing group indices[i] = new int[] { m.start(nGrp), m.end(nGrp) - 1 }; } // return match and list of indices int quality = QuickAccessEntry.MATCH_EXCELLENT; return new QuickAccessEntry(this, providerForMatching, indices, EMPTY_INDICES, quality ); } // String combinedLabel = (providerForMatching.getName() + " " + getLabel()); //$NON-NLS-1$ index = combinedLabel.toLowerCase().indexOf(filter); if (index != -1) { int lengthOfElementMatch = index + filter.length() - providerForMatching.getName().length() - 1; if (lengthOfElementMatch > 0) { return new QuickAccessEntry(this, providerForMatching, new int[][] { { 0, lengthOfElementMatch - 1 } }, new int[][] { { index, index + filter.length() - 1 } }, QuickAccessEntry.MATCH_GOOD); } return new QuickAccessEntry(this, providerForMatching, EMPTY_INDICES, new int[][] { { index, index + filter.length() - 1 } }, QuickAccessEntry.MATCH_GOOD); } String camelCase = CamelUtil.getCamelCase(sortLabel); index = camelCase.indexOf(filter); if (index != -1) { int[][] indices = CamelUtil.getCamelCaseIndices(sortLabel, index, filter .length()); return new QuickAccessEntry(this, providerForMatching, indices, EMPTY_INDICES, QuickAccessEntry.MATCH_GOOD); } String combinedCamelCase = CamelUtil.getCamelCase(combinedLabel); index = combinedCamelCase.indexOf(filter); if (index != -1) { String providerCamelCase = CamelUtil.getCamelCase(providerForMatching .getName()); int lengthOfElementMatch = index + filter.length() - providerCamelCase.length(); if (lengthOfElementMatch > 0) { return new QuickAccessEntry( this, providerForMatching, CamelUtil.getCamelCaseIndices(sortLabel, 0, lengthOfElementMatch), CamelUtil.getCamelCaseIndices(providerForMatching.getName(), index, filter.length() - lengthOfElementMatch), QuickAccessEntry.MATCH_GOOD); } return new QuickAccessEntry(this, providerForMatching, EMPTY_INDICES, CamelUtil.getCamelCaseIndices(providerForMatching .getName(), index, filter.length()), QuickAccessEntry.MATCH_GOOD); } return null; } }